Smoothing trajectories¶

Binder IPYNB HTML

To smooth trajectories, we can use a Kalman filter. The implemented KalmanSmootherCV is based on the assumption of a nearly-constant velocity (CV) model. To use KalmanSmootherCV, the optional dependency StoneSoup needs to be installed.

Documentation

In [1]:
import pandas as pd
import geopandas as gpd
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
import movingpandas as mpd

import warnings
warnings.filterwarnings('ignore')

mpd.show_versions()
MovingPandas 0.9.rc2

SYSTEM INFO
-----------
python     : 3.7.12 | packaged by conda-forge | (default, Oct 26 2021, 05:37:49) [MSC v.1916 64 bit (AMD64)]
executable : E:\Anaconda\envs\mpd-ex\python.exe
machine    : Windows-10-10.0.19041-SP0

GEOS, GDAL, PROJ INFO
---------------------
GEOS       : None
GEOS lib   : None
GDAL       : 3.2.1
GDAL data dir: None
PROJ       : 7.2.0
PROJ data dir: E:\Anaconda\envs\mpd-ex\Library\share\proj

PYTHON DEPENDENCIES
-------------------
geopandas  : 0.10.2
pandas     : 1.3.5
fiona      : 1.8.18
numpy      : 1.21.5
shapely    : 1.7.1
rtree      : 0.9.7
pyproj     : 3.1.0
matplotlib : 3.5.1
mapclassify: 2.4.3
geopy      : 2.2.0
holoviews  : 1.14.6
hvplot     : 0.7.3
geoviews   : 1.9.2
In [2]:
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
In [3]:
split = mpd.ObservationGapSplitter(traj_collection).split(gap=timedelta(minutes=15))
In [4]:
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
split.plot(column='trajectory_id', **plot_defaults)
Out[4]:
<AxesSubplot:>

KalmanSmootherCV¶

This smoother operates on the assumption of a nearly-constant velocity (CV) model. The process_noise_std and measurement_noise_std parameters can be used to tune the smoother:

  • process_noise_std governs the uncertainty associated with the adherence of the new (smooth) trajectories to the CV model assumption; higher values relax the assumption, therefore leading to less-smooth trajectories, and vice-versa.
  • measurement_noise_std controls the assumed error in the original trajectories; higher values dictate that the original trajectories are expected to be noisier (and therefore, less reliable), thus leading to smoother trajectories, and vice-versa.

Try tuning these parameters and observe the resulting trajectories:

In [5]:
help(mpd.KalmanSmootherCV)
Help on class KalmanSmootherCV in module movingpandas.trajectory_smoother:

class KalmanSmootherCV(TrajectorySmoother)
 |  KalmanSmootherCV(traj)
 |  
 |  Smooths using a Kalman Filter with a Constant Velocity model.
 |  
 |  The Constant Velocity model assumes that the speed between consecutive locations is
 |  nearly constant. For trajectories where ``traj.is_latlon = True`` the smoother
 |  converts to EPSG:3395 (World Mercator) internally to perform filtering and smoothing
 |  
 |  .. note::
 |      This class makes use of
 |      `Stone Soup <https://stonesoup.readthedocs.io/en/latest/>`_, which is an
 |      optional dependency and not installed by default. To use this class, you need
 |      to install Stone Soup directly
 |      (see `here <https://stonesoup.readthedocs.io/en/latest/#installation>`_).
 |  
 |  Method resolution order:
 |      KalmanSmootherCV
 |      TrajectorySmoother
 |      abc.ABC
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  smooth(self, process_noise_std=0.5, measurement_noise_std=1)
 |      Smooth the input Trajectory/TrajectoryCollection
 |      
 |      Parameters
 |      ----------
 |      process_noise_std: float or sequence of floats of length 2, default is 1e-5.
 |          The process (acceleration) noise standard deviation.
 |      
 |          If a sequence (e.g. list, tuple, etc.) is provided, the first index is used
 |          for the x coordinate, while the second is used for the y coordinate. If
 |          ``traj.is_latlon=True`` the values are applied to the  easting and northing
 |          coordinate (in EPSG:3395) respectively.
 |      
 |          Alternatively, a single float can be provided, which is assumed to be the
 |          same for both coordinates.
 |      measurement_noise_std: float or sequence of floats of length 2, default is 1.
 |          The measurement noise standard deviation.
 |      
 |          If a sequence (e.g. list, tuple, etc.) is provided, the first index is used
 |          for the x coordinate, while the second is used for the y coordinate. If
 |          ``traj.is_latlon=True`` the values are applied to the  easting and northing
 |          coordinate (in EPSG:3395) respectively.
 |      
 |          Alternatively, a single float can be provided, which is assumed to be the
 |          same for both coordinates.
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __abstractmethods__ = frozenset()
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from TrajectorySmoother:
 |  
 |  __init__(self, traj)
 |      Create TrajectorySmoother
 |      
 |      Parameters
 |      ----------
 |      traj : Trajectory or TrajectoryCollection
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from TrajectorySmoother:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

In [6]:
smooth = mpd.trajectory_smoother.KalmanSmootherCV(split).smooth(process_noise_std=0.1, measurement_noise_std=10)
print(smooth)
TrajectoryCollection with 11 trajectories
In [7]:
hvplot_defaults = {'tiles':'CartoLight', 'frame_height':320, 'frame_width':320, 'cmap':'Viridis', 'colorbar':True}
kwargs = {**hvplot_defaults, 'line_width':4}
(split.hvplot(title='Original Trajectories', **kwargs) + 
 smooth.hvplot(title='Smooth Trajectories', **kwargs))
Out[7]:
In [8]:
kwargs = {**hvplot_defaults, 'c':'speed', 'line_width':7, 'clim':(0,20)}
(split.trajectories[2].hvplot(title='Original Trajectories', **kwargs) + 
 smooth.trajectories[2].hvplot(title='Smooth Trajectories', **kwargs))
Out[8]:
In [ ]: